package org.junyee.demo.security.ec;

import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import com.sun.org.apache.xml.internal.security.algorithms.implementations.ECDSAUtils;

import org.junyee.demo.security.exception.KeyException;
import org.springframework.util.Assert;

/**
 * Refrence JOSE BigIntegerUtil.java
 */
@SuppressWarnings("restriction")
public class EcUtils {

    static final KeyFactory KEY_FACTORY;
    public static final String SECURITY_SUN_EC_DSA_UTIL_DISABLE = "security.sun.ec.dsa.util.disable";

    private static boolean disableSecuritySunEcDsaUtil() {
        return Boolean.parseBoolean(System.getProperty(SECURITY_SUN_EC_DSA_UTIL_DISABLE, "false"));
    }
    
    static {
        try {
            KEY_FACTORY = KeyFactory.getInstance("EC");
        } catch (NoSuchAlgorithmException e) {
            throw new KeyException(e);
        }
    }

    public static byte[] toBytesUnsigned(final BigInteger bigInt) {
        Assert.notNull(bigInt, "Args Required");
        // Copied from Apache Commons Codec 1.8

        int bitlen = bigInt.bitLength();

        // round bitlen
        bitlen = ((bitlen + 7) >> 3) << 3;
        final byte[] bigBytes = bigInt.toByteArray();

        if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {

            return bigBytes;

        }

        // set up params for copying everything but sign bit
        int startSrc = 0;
        int len = bigBytes.length;

        // if bigInt is exactly byte-aligned, just skip signbit in copy
        if ((bigInt.bitLength() % 8) == 0) {

            startSrc = 1;
            len--;
        }

        final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
        final byte[] resizedBytes = new byte[bitlen / 8];
        System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
        return resizedBytes;
    }

    

    /**
     * 
     * @param derSignature
     * @param outputLength
     * @return
     */
    public static byte[] transcodeSignatureToConcat(final byte[] derSignature) {
        Assert.notNull(derSignature, "Der Signature Required ");
        if (!disableSecuritySunEcDsaUtil()) {
            try {
                return ECDSAUtils.convertASN1toXMLDSIG(derSignature);
            } catch (IOException e) {
                throw new KeyException(e);
            }
        }

        if (derSignature.length < 8 || derSignature[0] != 48) {
            throw new KeyException("Invalid ASN.1 format of ECDSA signature");
        }

        int offset;
        if (derSignature[1] > 0) {
            offset = 2;
        } else if (derSignature[1] == (byte) 0x81) {
            offset = 3;
        } else {
            throw new KeyException("Invalid ASN.1 format of ECDSA signature");
        }

        byte rLength = derSignature[offset + 1];

        int i = rLength;
        while ((i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0)) {
            i--;
        }

        byte sLength = derSignature[offset + 2 + rLength + 1];

        int j = sLength;
        while ((j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0)) {
            j--;
        }

        int rawLen = Math.max(i, j);

        if ((derSignature[offset - 1] & 0xff) != derSignature.length - offset
                || (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength || derSignature[offset] != 2
                || derSignature[offset + 2 + rLength] != 2) {
            throw new KeyException("Invalid ASN.1 format of ECDSA signature");
        }

        final byte[] concatSignature = new byte[2 * rawLen];

        System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i);
        System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j);

        return concatSignature;
    }

    /**
     * 
     * @param esSignature
     * @return
     */
    public static byte[] transcodeSignatureToDER(byte[] esSignature) {
        Assert.notNull(esSignature, "ES Signature Required ");
        if (!disableSecuritySunEcDsaUtil()) {
            try {
                return ECDSAUtils.convertXMLDSIGtoASN1(esSignature);
            } catch (IOException e) {
                throw new KeyException(e);
            }
        }
        int rawLen = esSignature.length / 2;

        int i = rawLen;

        while ((i > 0) && (esSignature[rawLen - i] == 0)) {
            i--;
        }

        int j = i;

        if (esSignature[rawLen - i] < 0) {
            j += 1;
        }

        int k = rawLen;

        while ((k > 0) && (esSignature[2 * rawLen - k] == 0)) {
            k--;
        }

        int l = k;

        if (esSignature[2 * rawLen - k] < 0) {
            l += 1;
        }

        int len = 2 + j + 2 + l;

        if (len > 255) {
            throw new KeyException("Invalid XMLDSIG format of ECDSA signature");
        }

        int offset;

        final byte derSignature[];

        if (len < 128) {
            derSignature = new byte[2 + 2 + j + 2 + l];
            offset = 1;
        } else {
            derSignature = new byte[3 + 2 + j + 2 + l];
            derSignature[1] = (byte) 0x81;
            offset = 2;
        }

        derSignature[0] = 48;
        derSignature[offset++] = (byte) len;
        derSignature[offset++] = 2;
        derSignature[offset++] = (byte) j;

        System.arraycopy(esSignature, rawLen - i, derSignature, (offset + j) - i, i);

        offset += j;

        derSignature[offset++] = 2;
        derSignature[offset++] = (byte) l;

        System.arraycopy(esSignature, 2 * rawLen - k, derSignature, (offset + l) - k, k);

        return derSignature;
    }

    private EcUtils() {

    }
}
